import os
import re

from py_ini_config_modules.system_functions import eprint, read_ini_file, trim_empty_strings, \
    get_all_files_to_recursive_scan


def ini_config_set(file, section, var, value, noindent, com_symbol, view_comments, separator=None):
    """
    Устанавливает значение переменной
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param value: значение для установки
    :param noindent: если True, то в файле конфига удаляются отступы
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param view_comments: если True, то при установке значения переменной просматриваются закомментированные строки
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список строк обновлённого файла конфига
    """
    trim_empty_strings(file)
    if separator is None:
        separator = '='
    config = read_ini_file(file)
    written = False
    current_section = False
    section_start_index = -1
    commented_index = -1
    uncommented_index = -1

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_start_index = i
            else:
                current_section = False
        if re.fullmatch(f'\s*{com_symbol}*\s*{var}\s*', line[0]) and current_section:
            if com_symbol not in config[i]:
                uncommented_index = i
            elif len(line) > 1 and line[1] == value:
                commented_index = i

    if commented_index != -1 and view_comments:  # 1, 3
        if separator != separator.strip():
            config[commented_index] = \
                config[commented_index].lstrip().lstrip(com_symbol)
            config[commented_index] = config[commented_index][:config[commented_index].find(var)] + \
                                      var + separator + value
        else:
            config[commented_index] = config[commented_index].lstrip().lstrip(com_symbol)
        written = True
        if uncommented_index != -1:
            config[uncommented_index] = com_symbol + config[uncommented_index]
        if noindent:
            config[commented_index] = config[commented_index].lstrip()
    elif uncommented_index != -1:  # 2
        if separator.strip() not in config[uncommented_index]:
            # config[i] = var + separator + value
            config[uncommented_index] = config[uncommented_index].rstrip() + separator + value
        elif separator != separator.strip():
            indx = config[uncommented_index].index(separator.strip()) - 1
            while config[uncommented_index][indx] == ' ':
                indx -= 1
            config[uncommented_index] = config[uncommented_index][:indx + 1] + separator + value
        else:
            indx = config[uncommented_index].index(separator.strip()) + 1
            while indx < len(config[uncommented_index]) and config[uncommented_index][indx] == ' ':
                indx += 1
            config[uncommented_index] = config[uncommented_index][:indx] + value
        written = True
        # if config[i].lstrip().startswith(com_symbol):
        #     config[i] = config[i].lstrip().lstrip(com_symbol).lstrip()
        # written = True
        if noindent:
            config[uncommented_index] = config[uncommented_index].lstrip()
    if not written:
        if section_start_index != -1:
            config.insert(section_start_index + 1, var + separator + value)
        else:
            if len(config) > 0 and config[-1].strip() != '':
                config.append('')
            elif len(config) == 0 or (len(config) == 1 and config[0].strip() == ''):
                config = []
            config.append('[' + section + ']')
            config.append(var + separator + value)
    return config


def ini_config_add(file, section, var, value, noindent, com_symbol, view_comments, separator=None,
                   values_separator=','):
    """
    Добавляет значение в список значений переменной
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param value: значение для установки
    :param noindent: если True, то в файле конфига удаляются отступы
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param view_comments: если True, то при установке значения переменной просматриваются закомментированные строки
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :param values_separator: символ, которым значения переменной отделены друг от друга, по умолчанию ,
    :return: список строк обновлённого файла конфига
    """
    trim_empty_strings(file)
    if separator is None:
        separator = '='
    config = read_ini_file(file)
    written = False
    current_section = False
    section_start_index = -1
    commented_index = -1
    uncommented_index = -1

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_start_index = i
            else:
                current_section = False
        if re.fullmatch(f'\s*{com_symbol}*\s*{var}\s*', line[0]) and current_section:
            if com_symbol not in config[i]:
                uncommented_index = i
            elif len(line) > 1 and value in line[1]:
                commented_index = i

    if commented_index != -1 and view_comments:  # 1, 3
        config[commented_index] = config[commented_index].lstrip().lstrip(com_symbol)
        written = True
        if uncommented_index != -1:
            config[uncommented_index] = com_symbol + config[uncommented_index]
        if noindent:
            config[commented_index] = config[commented_index].lstrip()
    elif uncommented_index != -1:  # 2
        if separator.strip() not in config[uncommented_index]:
            config[uncommented_index] = config[uncommented_index].rstrip() + separator + value
        elif separator != separator.strip():
            values = config[uncommented_index].replace(' ', '').split(separator)[1].split(values_separator)
            if not value in values:
                indx = config[uncommented_index].index(separator.strip()) - 1
                while config[uncommented_index][indx] == ' ':
                    indx -= 1
                config[uncommented_index] = config[uncommented_index][:indx + 1] + separator + \
                                            (values_separator.join(values + [value]) if values else value)
        else:
            values = config[uncommented_index].replace(' ', '').split(separator)[1].split(values_separator)
            if not value in values:
                config[uncommented_index] = config[uncommented_index].rstrip() + \
                                            (values_separator if values != [''] and values else '') + value
        written = True
        # if config[i].lstrip().startswith(com_symbol):
        #     config[i] = config[i].lstrip().lstrip(com_symbol).lstrip()
        # written = True
        if noindent:
            config[uncommented_index] = config[uncommented_index].lstrip()
    if not written:
        if section_start_index != -1:
            config.insert(section_start_index + 1, var + separator + value)
        else:
            if len(config) > 0 and config[-1].strip() != '':
                config.append('')
            elif len(config) == 0 or (len(config) == 1 and config[0].strip() == ''):
                config = []
            config.append('[' + section + ']')
            config.append(var + separator + value)
    return config


def ini_config_comment(file, section, com_symbol, var=None, separator='='):
    """
    Комментирует указанную переменную
    :param file: файл конфига
    :param section: секция
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param var: имя переменной
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список строк обновлённого файла конфига или False, если возникла ошибка
    """
    trim_empty_strings(file)
    config = read_ini_file(file)
    current_section = False
    section_found = False
    var_commented = False

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_found = True
            else:
                current_section = False
        elif current_section:
            if var is None:
                if not line[0].startswith(com_symbol):
                    config[i] = com_symbol + config[i]
            else:
                if line[0] == var:
                    config[i] = com_symbol + config[i]
                    var_commented = True
                # Переменные могут встречаться несколько раз!
                # elif line[0] == '#' + var:
                #     eprint('Переменная уже была закомментирована')
                #     return False
    if not section_found:
        eprint('Указанная секция не существует')
        return False
    if section_found and var is not None and not var_commented:
        eprint('Переменная не найдена')
        return False

    return config


def ini_config_del(file, section, var, flush, com_symbol, separator='=', value=None,
                   values_separator=','):
    """
    Удаляет указанную переменную из файла конфига
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param flush: если True и в секции не осталось переменных, удаляет секцию целиком
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :param value: значение переменной. Если указано, то функция удаляет не переменную, а данное значение из набора
    значений. Если значение единственное, удаляется переменная
    :param values_separator: символ, которым значения переменной отделены друг от друга, по умолчанию ,
    :return: список строк обновлённого файла конфига
    """
    trim_empty_strings(file)
    config = read_ini_file(file)
    new_config = []
    current_section = False
    section_found = False
    var_deleted = False
    vars_left_in_section = False
    section_index = -1
    last_commented_index = -1
    commented_vars_cnt = 0

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            new_config.append(config[i])
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_found = True
                section_index = i
            else:
                current_section = False
        elif current_section:
            if re.fullmatch(f'\s*{com_symbol}*\s*{var}\s*', line[0]):
                if not line[0].lstrip().startswith(com_symbol):
                    if value:
                        if len(line) > 1:
                            values = [v.strip() for v in line[1].split(values_separator)]
                            if value in values:
                                values.remove(value)
                                sep_next_index = config[i].index(separator) + 1
                                while sep_next_index < len(config[i]) and config[i][sep_next_index].isspace():
                                    sep_next_index += 1
                                config[i] = config[i][:sep_next_index] + values_separator.join(values)
                                var_deleted = True
                            else:
                                print(f'У переменной {var} в секции {section} нет значения {value}')
                                return False
                        else:
                            print(f'У переменной {var} нет значений')
                            return False
                    else:
                        var_deleted = True
                        continue
                else:
                    last_commented_index = i
                    commented_vars_cnt += 1
            else:
                vars_left_in_section = True
        new_config.append(config[i])

    if commented_vars_cnt > 1:
        vars_left_in_section = True
    if not section_found:
        eprint('Указанная секция не существует')
        return False
    elif not var_deleted:
        if last_commented_index != -1:
            if not value:
                new_config = new_config[:last_commented_index] + new_config[last_commented_index + 1:]
            else:
                values = new_config[last_commented_index].split(separator)
                if len(values) > 1:
                    values[1] = [v.strip() for v in values[1].split(values_separator)]
                    if value in values[1]:
                        values[1].remove(value)
                        new_config[last_commented_index] = values[0] + separator + values_separator.join(values[1])
                    else:
                        print(f'У переменной {var} в секции {section} нет значения {value}')
                        return False
                else:
                    print(f'У переменной {var} нет значений')
                    return False
        elif section_index != -1 and flush and not value:
            new_config = new_config[:section_index] + new_config[section_index + 1:]
        else:
            eprint('Переменная не найдена')
            return False
    if not vars_left_in_section and section_index != -1 and flush and not value:
        new_config = new_config[:section_index] + new_config[section_index + 1:]

    while len(new_config) > 0 and new_config[0].strip() == '':
        new_config = new_config[1:]
    while len(new_config) > 0 and new_config[-1].strip() == '':
        new_config = new_config[:-1]
    return new_config


def ini_config_get(file, section, var, separator='='):
    """
    Получает значение переменной из файла конфига
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: набор значений в столбик, если они есть; имя переменной, если она указана без значения;
    False, если переменной нет
    """
    config = read_ini_file(file)

    current_section = False

    res = []
    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
            else:
                current_section = False
        elif current_section:
            if var is not None:
                if line[0] == var:
                    return line[1] if len(line) > 1 else var
            else:
                res.append(raw_line)

    return False if res == [] else '\n'.join(res)


def ini_config_get_recursive(section, var, separator='=', inversed_order=False,
                             files_to_recursive_scan=None,
                             required_extension=None,
                             sort_collected=False,
                             shadowing=False,
                             verbose=False):
    """
    Просматривает все файлы в папках, которые образованы добавлением к имени файла .d
    Например, /etc/sddm.conf -> etc/sddm.conf.d/*
    Возвращает последнее найденное значение в файлах.
    :param verbose: вывод логов в stderr (True/False)
    :param shadowing: если указано, то файлы с одинаковыми именами, которые рассмотрены позднее,
    отменяют найденные значения в ранее рассмотренных, даже если таких переменных нет
    :param sort_collected: если указано, то все файлы, найденные в папках, сортируются по алфавиту по имени без пути,
    перед тем, как в них будут искать значения
    :param required_extension: расширение, файлы с которым рассматриваются в папках
    :param files_to_recursive_scan: список файлов, с имён которых начинаются папки *.d
    :param section: название секции поиска
    :param var: имя переменной
    :param separator: разделитель
    :param inversed_order: если True, то сначала просматриваются папки, в последнюю очередь указанные файлы;
    по умолчанию False
    :return: последнее найденное значение или False, если ни в одном файле не удалось найти значение
    """
    res = False
    files = []
    if files_to_recursive_scan:
        files = get_all_files_to_recursive_scan(
            files_to_recursive_scan,
            required_extension=required_extension,
            inversed_order=inversed_order,
            sort_collected=sort_collected,
            shadowing=shadowing,
            verbose=verbose
        )
    for recursive_added_file in files:
        temp_res = ini_config_get(recursive_added_file, section, var, separator)
        if temp_res:
            res = temp_res
    return res


def ini_config_get_commented(file, section, var, com_symbol, separator='='):
    """
    Получение значения закомментированной переменной
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список значений, если они есть; список из имени переменной, если она есть без значения; False, если её нет
    """
    config = read_ini_file(file)

    current_section = False
    res = []
    var_without_value_found = False

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
            else:
                current_section = False
        elif current_section:
            if re.fullmatch(f'\s*{com_symbol}\s*{var}\s*', line[0]):
                # return line[1] if len(line) > 1 else var
                if len(line) > 1:
                    res.append(line[1])
                else:
                    var_without_value_found = True
    if len(res) > 0:
        return res
    elif var_without_value_found:
        return [var]
    return False


def ini_config_is_set(file, section, var=None, value=None, separator='=',
                      values_separator=','):
    """
    Проверяет, есть ли у переменной значение
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param value: значение для установки
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :param values_separator: символ, которым значения переменной отделены друг от друга, по умолчанию ,
    :return: 0, если значение установлено (или если указано value и значение совпадает с указанным), иначе 1
    """
    config = read_ini_file(file)

    current_section = False

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                if var is None:
                    return 0
            else:
                current_section = False
        elif current_section:
            if line[0] == var and len(line) > 1:
                if value is None:
                    return 0
                else:
                    line[1] = [s.strip() for s in line[1].split(values_separator)]
                    if value in line[1]:
                        return 0
                    else:
                        return 1
    return 1


def ini_config_is_set_recursive(section, var=None, value=None, separator='=', values_separator=',',
                                files_to_recursive_scan=None,
                                shadowing=False,
                                sort_collected=False,
                                verbose=False,
                                required_extension=None):
    """
    Проверяет, есть ли у переменной значение в списке файлов рекурсивно.
    :param sort_collected: если указано, то все файлы, найденные в папках, сортируются по алфавиту по имени без пути,
    перед тем, как в них будут искать значения
    :param verbose: вывод логов в stderr (True/False)
    :param shadowing: если указано, то файлы с одинаковыми именами, которые рассмотрены позднее,
    отменяют найденные значения в ранее рассмотренных, даже если таких переменных нет
    :param section: секция поиска
    :param var: имя переменной
    :param value: значение, если его необходимо искать
    :param separator: разделитель между именем и значением переменной
    :param values_separator: разделитель значений переменных
    :param files_to_recursive_scan: список файлов для рекурсивного поиска в них и папках с именами файлов + '.d'
    :param required_extension: требуемое расширение для просматриваемых файлов
    :return: 0, если переменная найдена, иначе 1
    """
    files = []
    if files_to_recursive_scan:
        files = get_all_files_to_recursive_scan(
            files_to_recursive_scan,
            required_extension=required_extension,
            sort_collected=sort_collected,
            verbose=verbose,
            shadowing=shadowing
        )
    for recursive_added_file in files:
        if ini_config_is_set(recursive_added_file, section, var, value, separator, values_separator) == 0:
            return 0
    return 1


def ini_config_uncomment(file, section, com_symbol, var=None, separator='='):
    """
    Раскомментирует переменную в файле конфига
    :param file: файл конфига
    :param section: секция
    :param com_symbol: символ, с которого начинаются комментарии, по умолчанию #
    :param var: имя переменной
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список строк обновлённого файла конфига или False, если переменная уже была раскомментирована
    """
    trim_empty_strings(file)
    config = read_ini_file(file)
    current_section = False
    section_found = False
    var_uncommented = False
    last_commented_index = -1

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_found = True
            else:
                current_section = False
        elif current_section:
            if var is None:
                config[i] = config[i].lstrip().lstrip(com_symbol).lstrip()
            else:
                if line[0].startswith(com_symbol) and line[0].lstrip(com_symbol).lstrip() == var:
                    last_commented_index = i
                elif line[0] == var:
                    eprint('Переменная уже была раскомментирована')
                    return False

    if last_commented_index != -1:
        config[last_commented_index] = config[last_commented_index].lstrip().lstrip(com_symbol)
        var_uncommented = True
    if not section_found:
        eprint('Указанная секция не существует')
        return False
    elif var is not None and not var_uncommented:
        eprint('Переменная не найдена')
        return False
    return config


def ini_config_del_section(file, section, separator='='):
    """
    Удаляет секцию из файла конфига
    :param file: файл конфига
    :param section: секция
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список строк обновлённого файла конфига или False, если секция не существует
    """
    trim_empty_strings(file)
    config = read_ini_file(file)
    current_section = False
    section_found = False
    new_config = []

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            if not current_section:
                new_config.append(line)
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_found = True
            else:
                current_section = False
        if current_section:
            continue
        new_config.append(config[i])

    if not section_found:
        eprint('Указанная секция не существует')
        return False

    while len(new_config) > 0 and new_config[0].strip() == '':
        new_config = new_config[1:]
    while len(new_config) > 0 and new_config[-1].strip() == '':
        new_config = new_config[:-1]
    return new_config


def ini_config_is_empty(file, section, separator='='):
    """
    Проверяет, является ли секция файла конфига пустой
    :param file: файл конфига
    :param section: секция
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: 0, если секция пустая, 1 в противном случае
    """
    config = read_ini_file(file)
    current_section = False
    section_is_empty = True
    section_found = False
    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
                section_found = True
            else:
                current_section = False
        elif current_section:
            section_is_empty = False
    if not section_found:
        eprint('Указанная секция не существует')
        return False

    return not section_is_empty


def ini_config_rename(file, section, var, new_name, separator='='):
    """
    Переименовывает имя переменной в файле конфига
    :param file: файл конфига
    :param section: секция
    :param var: имя переменной
    :param new_name: новое имя переменной
    :param separator: разделитель между именами переменных и их значениями, по умолчанию =
    :return: список строк обновлённого файла конфига или False, если указанная переменная не существует
    """
    trim_empty_strings(file)
    config = read_ini_file(file)

    current_section = False

    for i in range(len(config)):
        raw_line = config[i]
        line = raw_line.strip()
        if line == '':
            continue
        line = line.split(separator.strip(), 1)
        for j in range(len(line)):
            line[j] = line[j].strip()
        if len(line) == 1 and line[0].startswith('['):
            line[0] = line[0][1:-1]
            if line[0] == section:
                current_section = True
            else:
                current_section = False
        elif current_section:
            if line[0] == var:
                config[i] = config[i].replace(var, new_name, 1)
                return config
    eprint('Переменная не найдена')
    return False
